/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

package com.aphrodite.util.math;

import com.aphrodite.util.CharUtil;
import com.aphrodite.util.StringUtil;

/**
 * Perform math operations on string based expressions
 * 
 * @author kentelt
 * 
 */
public class MathUtil {
	private static MathUtil calculator = null;

	public static Integer decode(String s) throws NumberFormatException {
		byte byte0 = 10;
		int i = 0;
		boolean flag = false;
		if (s.startsWith("-")) {
			flag = true;
			i++;
		}
		if (s.startsWith("0x", i) || s.startsWith("0X", i)) {
			i += 2;
			byte0 = 16;
		} else if (s.startsWith("#", i)) {
			i++;
			byte0 = 16;
		} else if (s.startsWith("0", i) && s.length() > 1 + i) {
			i++;
			byte0 = 8;
		}
		if (s.startsWith("-", i))
			throw new NumberFormatException("Negative sign in wrong position");
		Integer integer;
		try {
			integer = Integer.valueOf(s.substring(i), byte0);
			integer = flag ? new Integer(-integer.intValue()) : integer;
		} catch (NumberFormatException numberformatexception) {
			String s1 = flag ? new String((new StringBuffer()).append("-").append(s.substring(i)).toString()) : s
					.substring(i);
			integer = Integer.valueOf(s1, byte0);
		}
		return integer;
	}

	public static MathUtil getInstance() {
		if (calculator == null)
			calculator = new MathUtil();
		return calculator;
	}

	/**
	 * Finds the index of the smallest element
	 * 
	 * @param elements
	 *            the elements
	 * @return the index of the smallest element
	 */
	public static int indexOfLeast(int[] elements) {
		int index = 0;
		int least = elements[0];
		for (int i = 1; i < elements.length; i++) {
			if (elements[i] < least) {
				index = i;
				least = elements[i];
			}
		}
		return index;
	}

	// #endif

	/**
	 * Rounds the given double value
	 * 
	 * @param value
	 *            the value
	 * @return the rounded value, x.5 and higher is rounded to x + 1.
	 * @since CLDC 1.1
	 */
	public static long round(double value) {
		if (value < 0) {
			return (long) (value - 0.5);
		} else {
			return (long) (value + 0.5);
		}
	}

	// #if polish.hasFloatingPoint
	/**
	 * Rounds the given float value
	 * 
	 * @param value
	 *            the value
	 * @return the rounded value, x.5 and higher is rounded to x + 1.
	 * @since CLDC 1.1
	 */
	public static int round(float value) {
		if (value < 0) {
			return (int) (value - 0.5);
		} else {
			return (int) (value + 0.5);
		}
	}

	private MathUtil() {}

	public String Acos(String s) {
		int j;
		String s1;
		for (; s.indexOf("acos") >= 0; s = s.substring(0, s.indexOf("acos")) + s1 + s.substring(j + 1, s.length())) {
			int i = 0;
			j = 0;
			i = s.indexOf("acos") + 4;
			for (int k = s.indexOf("acos") + 4; k < s.length() && CharUtil.isDigit(s.charAt(k)); k++)
				j = k;

			long l = Long.parseLong(s.substring(i, j + 1));
			s1 = MathEngine.acos(l) + "";
		}

		return s;
	}

	public String Asin(String s) {
		int j;
		String s1;
		for (; s.indexOf("asin") >= 0; s = s.substring(0, s.indexOf("asin")) + s1 + s.substring(j + 1, s.length())) {
			int i = 0;
			j = 0;
			i = s.indexOf("asin") + 4;
			for (int k = s.indexOf("asin") + 4; k < s.length() && CharUtil.isDigit(s.charAt(k)); k++)
				j = k;

			long l = Long.parseLong(s.substring(i, j + 1));
			s1 = MathEngine.asin(l) + "";
		}

		return s;
	}

	public String Atan(String s) {
		int j;
		String s1;
		for (; s.indexOf("atan") >= 0; s = s.substring(0, s.indexOf("atan")) + s1 + s.substring(j + 1, s.length())) {
			int i = 0;
			j = 0;
			i = s.indexOf("atan") + 4;
			for (int k = s.indexOf("atan") + 4; k < s.length() && CharUtil.isDigit(s.charAt(k)); k++)
				j = k;

			long l = Long.parseLong(s.substring(i, j + 1));
			s1 = MathEngine.atan(l) + "";
		}

		return s;
	}

	public String convertFloatToLong(String s) {
		String s1 = "";
		// String s2 = "";
		// boolean flag = false;
		int j = 0;
		for (int k = 0; k < s.length(); k++)
			if (CharUtil.isDigit(s.charAt(k))) {
				int i = k;
				for (int l = k; l < s.length(); l++) {
					if (!CharUtil.isDigit(s.charAt(l))) {
						j = l;
						break;
					}
					if (l == s.length() - 1)
						j = l + 1;
					k = l;
				}

				String s3 = s.substring(i, j);
				s1 = s1 + MathEngine.toFP(s3) + "";
			} else {
				s1 = s1 + s.charAt(k);
			}

		for (; s1.indexOf("e") >= 0; s1 = s1.substring(0, s1.indexOf("e")) + MathEngine.E
				+ s1.substring(s1.indexOf("e") + 1, s1.length()))
			;
		for (; s1.indexOf("pi") >= 0; s1 = s1.substring(0, s1.indexOf("pi")) + MathEngine.PI
				+ s1.substring(s1.indexOf("pi") + 2, s1.length()))
			;
		return s1;
	}

	/**
	 * returns the trigonometric cosine of an angle given as a string<br/>
	 * <b>eg</b><br/> cos 45
	 * 
	 * @param s
	 *            angle
	 * @return String
	 */
	public String cos(String s) {
		int j;
		String s1;
		for (; s.indexOf("cos") >= 0; s = s.substring(0, s.indexOf("cos")) + s1 + s.substring(j + 1, s.length())) {
			int i = 0;
			j = 0;
			i = s.indexOf("cos") + 3;
			for (int k = s.indexOf("cos") + 3; k < s.length() && CharUtil.isDigit(s.charAt(k)); k++)
				j = k;

			long l = Long.parseLong(s.substring(i, j + 1));
			s1 = MathEngine.cos(l) + "";
		}

		return s;
	}

	/**
	 * Evaluates a numeric expression thats in a string format <br/>
	 * <b>Eg</b><br/> String s="45+((67+78)+(56-6))+sin 90+(56^5)";<br/> String
	 * result=evaluateNumericExpression(s);
	 * 
	 * @param s
	 *            The math expression
	 * @return
	 * @throws Exception
	 * 
	 *             String
	 */
	public String evaluateNumericExpression(String s) throws Exception {
		s = StringUtil.removeAllSpaces(s);
		s = fixNegativeNumbers(s);
		s = convertFloatToLong(s);
		s = solveNumeric(s);
		s = Long.toString(Long.parseLong(s));
		return s;
	}

	private String fixNegativeNumbers(String s) {
		for (int i = 0; i < s.length(); i++) {
			if (s.charAt(i) != '-')
				continue;
			if (i - 1 >= 0 && (CharUtil.isDigit(s.charAt(i - 1)) || s.charAt(i - 1) == ')')) {
				if (s.length() > i + 2 && s.charAt(i + 2) != '^')
					s = s.substring(0, i) + "+" + s.substring(i, s.length());
				continue;
			}
			if (s.length() > i + 1 && s.charAt(i + 1) == '(')
				s = s.substring(0, i) + "-1*" + s.substring(i + 1, s.length());
		}

		return s;
	}

	/**
	 * get random number in a range
	 * 
	 * @param min
	 * @param max
	 * @return int
	 */
	public int getRandomNumber(int min, int max) {
		java.util.Random rnd = new java.util.Random();
		return min + Math.abs(rnd.nextInt()) % (max - min);
	}

	public String pwr(String s) {
		int i;
		int l;
		String s1;
		for (; s.indexOf("^") > 0; s = s.substring(0, i) + s1 + s.substring(l + 1, s.length())) {
			i = 0;
			int j = 0;
			int k = 0;
			l = 0;
			j = s.indexOf("^");
			for (int i1 = s.indexOf("^") - 1; i1 >= 0 && CharUtil.isDigit(s.charAt(i1)); i1--)
				i = i1;

			long l1 = Long.parseLong(s.substring(i, j));
			k = s.indexOf("^") + 1;
			for (int j1 = s.indexOf("^") + 1; j1 < s.length() && CharUtil.isDigit(s.charAt(j1)); j1++)
				l = j1;

			long l2 = Long.parseLong(s.substring(k, l + 1));
			s1 = "";
			if (l1 < 0L) {
				String s2 = Long.toString(l2);
				int k1 = Integer.parseInt(s2.substring(0, s2.indexOf(".")));
				s1 = MathEngine.pow(Math.abs(l1), l2) + "";
				if (k1 != (k1 / 2) * 2)
					s1 = "-" + s1;
			} else {
				s1 = MathEngine.pow(l1, l2) + "";
			}
		}

		return s;
	}

	/**
	 * returns the trigonometric sine of an angle given as a string<br/>
	 * <b>eg</b><br/> sin 45
	 * 
	 * @param s
	 *            angle
	 * @return String
	 */
	public String sin(String s) {
		int j;
		String s1;
		for (; s.indexOf("sin") >= 0; s = s.substring(0, s.indexOf("sin")) + s1 + s.substring(j + 1, s.length())) {
			int i = 0;
			j = 0;
			i = s.indexOf("sin") + 3;
			for (int k = s.indexOf("sin") + 3; k < s.length() && CharUtil.isDigit(s.charAt(k)); k++)
				j = k;

			long l = Long.parseLong(s.substring(i, j + 1));
			s1 = MathEngine.sin(l) + "";
		}

		return s;
	}

	public String solveAbs(String s) {
		int j;
		String s1;
		for (; s.indexOf("abs") >= 0; s = s.substring(0, s.indexOf("abs")) + s1 + s.substring(j + 1, s.length())) {
			int i = 0;
			j = 0;
			i = s.indexOf("abs") + 3;
			for (int k = s.indexOf("abs") + 3; k < s.length() && CharUtil.isDigit(s.charAt(k)); k++)
				j = k;

			long l = Long.parseLong(s.substring(i, j + 1));
			s1 = MathEngine.abs(l) + "";
		}

		return s;
	}

	private String solveDiv(String s) {
		int i;
		int l;
		String s1;
		for (; s.indexOf("/") > 0; s = s.substring(0, i) + s1 + s.substring(l + 1, s.length())) {
			i = 0;
			int j = 0;
			int k = 0;
			l = 0;
			j = s.indexOf("/");
			for (int i1 = s.indexOf("/") - 1; i1 >= 0 && CharUtil.isDigit(s.charAt(i1)); i1--)
				i = i1;
			long l1 = Long.parseLong(s.substring(i, j));
			k = s.indexOf("/") + 1;
			for (int j1 = s.indexOf("/") + 1; j1 < s.length() && CharUtil.isDigit(s.charAt(j1)); j1++)
				l = j1;

			long l2 = Long.parseLong(s.substring(k, l + 1));
			s1 = MathEngine.div(l1, l2) + "";
		}

		return s;
	}

	public String solveLn(String s) {
		int j;
		String s1;
		for (; s.indexOf("ln") >= 0; s = s.substring(0, s.indexOf("ln")) + s1 + s.substring(j + 1, s.length())) {
			int i = 0;
			j = 0;
			i = s.indexOf("ln") + 2;
			for (int k = s.indexOf("ln") + 2; k < s.length() && CharUtil.isDigit(s.charAt(k)); k++)
				j = k;

			long l = Long.parseLong(s.substring(i, j + 1));
			s1 = MathEngine.log(l) + "";
		}

		return s;
	}

	private String solveMult(String s) {
		int i;
		int l;
		String s1;
		for (; s.indexOf("*") > 0; s = s.substring(0, i) + s1 + s.substring(l + 1, s.length())) {
			i = 0;
			int j = 0;
			int k = 0;
			l = 0;
			j = s.indexOf("*");
			for (int i1 = s.indexOf("*") - 1; i1 >= 0 && CharUtil.isDigit(s.charAt(i1)); i1--)
				i = i1;

			long l1 = Long.parseLong(s.substring(i, j));
			k = s.indexOf("*") + 1;
			for (int j1 = s.indexOf("*") + 1; j1 < s.length() && CharUtil.isDigit(s.charAt(j1)); j1++)
				l = j1;

			long l2 = Long.parseLong(s.substring(k, l + 1));
			s1 = MathEngine.mul(l1, l2) + "";
		}

		return s;
	}

	private String solveNumeric(String s) throws Exception {
		boolean flag = true;
		do {
			if (!flag)
				break;
			flag = false;
			int i = 0;
			int j = 0;
			int k = 0;
			for (int l = 0; l < s.length(); l++) {
				if (s.charAt(l) == '(') {
					if (!flag) {
						i = l;
						flag = true;
					} else {
						k++;
					}
					continue;
				}
				if (s.charAt(l) != ')')
					continue;
				j = l;
				if (k == 0)
					break;
				k--;
			}

			if (flag)
				s = s.substring(0, i) + solveNumeric(s.substring(i + 1, j)) + s.substring(j + 1, s.length());
		} while (true);
		s = solveAbs(s);
		s = solveLn(s);
		s = Asin(s);
		s = Acos(s);
		s = Atan(s);
		s = sin(s);
		s = cos(s);
		s = tan(s);
		s = pwr(s);
		s = sqrt(s);
		s = solveDiv(s);
		s = solveMult(s);
		s = solveSum(s);
		return s;
	}

	public String solveSum(String s) {
		int i;
		int l;
		String s1;
		for (; s.indexOf("+") > 0; s = s.substring(0, i) + s1 + s.substring(l + 1, s.length())) {
			i = 0;
			int j = 0;
			int k = 0;
			l = 0;
			j = s.indexOf("+");
			for (int i1 = s.indexOf("+") - 1; i1 >= 0 && CharUtil.isDigit(s.charAt(i1)); i1--)
				i = i1;

			long l1 = Long.parseLong(s.substring(i, j));
			k = s.indexOf("+") + 1;
			for (int j1 = s.indexOf("+") + 1; j1 < s.length() && CharUtil.isDigit(s.charAt(j1)); j1++)
				l = j1;

			long l2 = Long.parseLong(s.substring(k, l + 1));
			long temp = l1 + l2;
			s1 = temp + "";
		}

		return s;
	}

	public String sqrt(String s) {
		int j;
		String s1;
		for (; s.indexOf("sqrt") >= 0; s = s.substring(0, s.indexOf("sqrt")) + s1 + s.substring(j + 1, s.length())) {
			int i = 0;
			j = 0;
			i = s.indexOf("sqrt") + 4;
			for (int k = s.indexOf("sqrt") + 4; k < s.length() && CharUtil.isDigit(s.charAt(k)); k++)
				j = k;

			long l = Long.parseLong(s.substring(i, j + 1));
			s1 = MathEngine.sqrt(l) + "";
		}

		return s;
	}

	/**
	 * returns the trigonometric tangent of an angle given as a string<br/>
	 * <b>eg</b><br/> tan 45
	 * 
	 * @param s
	 *            angle
	 * @return String
	 */
	public String tan(String s) {
		int j;
		String s1;
		for (; s.indexOf("tan") >= 0; s = s.substring(0, s.indexOf("tan")) + s1 + s.substring(j + 1, s.length())) {
			int i = 0;
			j = 0;
			i = s.indexOf("tan") + 3;
			for (int k = s.indexOf("tan") + 3; k < s.length() && CharUtil.isDigit(s.charAt(k)); k++)
				j = k;

			long l = Long.parseLong(s.substring(i, j + 1));
			s1 = MathEngine.tan(l) + "";
		}

		return s;
	}

}
